// ====== Computer Graphics Experiment #9 ======
// |            Cylinder rendering             |
// =============================================
//
// Requirement:
// (1) Implement cylinder rendering function.
// (2) Change polygon drawing mode and face culling parameters
//     and observe the effects.
// (3) Change smooth shading to flat shading and observe the effects
// (4) Carefully read and understand the rest of the source code

#include <windows.h>
#include <GL/glut.h>
#include <math.h>

#define PI 3.14159265
float xrotate, yrotate, zrotate;

void drawsurface(float radius, float height, int nslice, int nstack)
// nslice --- Number of subdivision around z-axis
// nstack --- Number of subdivision along z-axis
{
	
	//Write your code here	
}

void drawDisk(float radius, int nslice, int nring)
// nslice --- Number of subdivision around z-axis
// nring  --- Number of concentric rings on each end face
{
	glNormal3f(0,0,1);
	// Draw quads
	//Write your code here

    // Draw triangles around center
	//Write your code here
	
}

// render a cylinder centered at the origin, with z as axis.
void MyCylinder(float radius, float height, 
				int nslice, int nstack, int nring)
// nslice --- Number of subdivision around z-axis
// nstack --- Number of subdivision along z-axis
// nring  --- Number of concentric rings on each end face
{
	drawsurface(radius,height,nslice,nstack);
	glTranslatef(0,0,height/2);
	drawDisk(radius,nslice,nring);
	glTranslatef(0,0,-height);
	glRotatef(180,1,0,0);
	drawDisk(radius,nslice,nring);

}
class CVector3D
{
public:
	float x, y, z;

	// Constructors
	CVector3D(void) {x=0.0; y=0.0; z=0.0;}
	CVector3D(float x0, float y0, float z0)
	{x=x0; y=y0; z=z0;}
};

// View reference frame class
class CViewFrame
{
public:
	float step; // step size
	float turn_a; // turn angle
	float pitch_a; // pitch angle
	float roll_a; // roll angle

	CVector3D P0; // View reference point
	CVector3D u; // unit vector in xv direction
	CVector3D v; // unit vector in yv direction
	CVector3D n; // unit vector in zv direction

	void move_up(void) 
	{
		
	}

	void move_down(void) 
	{
		
	}

	void move_left(void) 
	{
		
	}

	void move_right(void) 
	{
		
	}

	void move_forward(void) 
	{
		
	}

	void move_backward(void) 
	{
		
	}

	void turn_left(void)
	{
		
	}

	void turn_right(void)
	{
		
	}

	void look_up(void)
	{
		
	}

	void look_down(void)
	{
		
	}

	void roll_left(void)
	{
	
	}

	void roll_right(void)
	{
	
	}
};

CViewFrame view_frame;

int polygon_mode=0;
// 0 --- GL_FILL, 1 --- GL_LINE

int cull_face_mode=0;
// 0 --- Disable face culling, 1 --- Enable face culling

int face_to_cull=0;
// 0 --- Cull back face, 1 -- Cull front face

// Program window width and height
int pw_width, pw_height;

// Initialization function
void init(void)
{
	static GLfloat light_ambient[] = { 0.01, 0.01, 0.01, 1.0 };
	static GLfloat light_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
	static GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };
	static GLfloat light_pos[] = {50.0, 50.0, 200.0, 0.0};

	glClearColor (0.0, 0.0, 0.0, 0.0);
	glShadeModel (GL_SMOOTH); // Set shading model

	// Set light source properties for light source #0
	glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
	glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
	glLightfv(GL_LIGHT0, GL_POSITION, light_pos);

	glEnable(GL_LIGHTING); // Enable lighting
	glEnable(GL_LIGHT0); // Enable light source #0
	glEnable(GL_DEPTH_TEST); // Enable depth buffer test
	glEnable(GL_NORMALIZE); // Enable auto normalization
	// Enable two sided lighting
	glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1); 

	view_frame.P0=CVector3D(500.0, 0.0, 100.0);
	view_frame.u=CVector3D(0.0, 1.0, 0.0);
	view_frame.v=CVector3D(0.0, 0.0, 1.0);
	view_frame.n=CVector3D(1.0, 0.0, 0.0);
	view_frame.step=10;
	view_frame.turn_a=PI/18;
	view_frame.pitch_a=PI/18;
	view_frame.roll_a=PI/6;

	xrotate=0.0;
	yrotate=0.0;
    zrotate=0.0;
}

// Function to draw NULL-terminated string
void draw_string(void *font, char *str)
{
	while((*str)!='\0') {
		glutBitmapCharacter(font,(int)*str);
		str++;
	}
}

// Draw texts
void draw_texts(void)
{
	static char *str_polygon[2]={
		"Polygon Mode (Press p to change): GL_FILL",
		"Polygon Mode (Press p to change): GL_LINE"};
	static char *str_cull[2]={
		"Face culling (Press c to change): Disabled",
		"Face culling (Press c to change): Enabled"};
	static char *str_face[2]={
		"Face to cull (Press f to change): Back",
		"Face to cull (Press f to change): Front"};

	// Temporarily use orthographic projection
	// and set clipping window the same as program window
	glMatrixMode (GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity ();
	gluOrtho2D(0, pw_width, 0, pw_height);

	glColor3f(1.0, 1.0, 1.0);

	// Disable lighting before drawing texts
	glDisable(GL_LIGHTING); 

	// Draw texts
	glRasterPos2i(5, pw_height-20);
	draw_string(GLUT_BITMAP_9_BY_15, str_polygon[polygon_mode]);
	glRasterPos2i(5, pw_height-40);
	draw_string(GLUT_BITMAP_9_BY_15, str_cull[cull_face_mode]);
	glRasterPos2i(5, pw_height-60);
	draw_string(GLUT_BITMAP_9_BY_15, str_face[face_to_cull]);

	// Enable lighting after text drawing is finished
	glEnable(GL_LIGHTING); 

	// Restore projection matrix
	glPopMatrix();
}

// Display callback function
void display(void)
{
	GLfloat mat_color[] = { 0.91, 0.53, 0.14, 1.0 };

	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// Set material properties
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, mat_color);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_color);
	glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 64.0);

	// Set polygon drawing mode
	if (polygon_mode==0) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	else glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

	// Set face culling mode
	if (cull_face_mode==0) glDisable(GL_CULL_FACE);
	else glEnable(GL_CULL_FACE);

	// Define which faces to cull
	if (face_to_cull==0) glCullFace(GL_BACK);
	else glCullFace(GL_FRONT);

	// Set matrix mode to model view
	glMatrixMode (GL_MODELVIEW);
	glPushMatrix(); // Save current model view matrix
	
	CVector3D look_at;
	look_at.x=view_frame.P0.x-view_frame.n.x;
	look_at.y=view_frame.P0.y-view_frame.n.y;
	look_at.z=view_frame.P0.z-view_frame.n.z;
	gluLookAt(view_frame.P0.x, view_frame.P0.y, view_frame.P0.z,
		look_at.x, look_at.y, look_at.z,
		view_frame.v.x, view_frame.v.y, view_frame.v.z);
	
    
	// Render two crossing cylinders
	glRotatef(xrotate, 1.0, 0.0, 0.0);// rotate around x-axis
	glRotatef(yrotate, 0.0, 1.0, 0.0); //rotate around y-axis
    glRotatef(zrotate, 0.0, 0.0, 1.0); //rotate around z-axis

	MyCylinder(40.0, 200.0, 20, 10, 5);
	glRotatef(-90, 1.0, 0.0, 0.0);
	glTranslatef(0,100,0);
	MyCylinder(40.0, 200.0, 20, 10, 5);

	glPopMatrix(); // Restore model view matrix

	draw_texts(); // Draw texts

	glutSwapBuffers();
}

// Reshape callback function
void reshape (int w, int h)
{
	pw_width=w;	pw_height=h;

	// Set viewport as the entire program window
	glViewport (0, 0, w, h);

	// Set symmetric perspective projection
	glMatrixMode (GL_PROJECTION);
	glLoadIdentity ();
	gluPerspective(60.0, (float)w/(float)h, 10.0, 100000.0);

	// Reset modelview transformation matrix to identity
	glMatrixMode (GL_MODELVIEW);
	glLoadIdentity ();
}

// Keyboard callback function
void keyboard (unsigned char key, int x, int y)
{
	switch (key) 
	{
		case 27:
			exit(0); break;

		// Press P to change polygon drawing mode
		case 'p': 
		case 'P':
			polygon_mode=1-polygon_mode;
			glutPostRedisplay(); break;

		// Press C to change face culling mode
		case 'c': 
		case 'C':
			cull_face_mode=1-cull_face_mode;
			glutPostRedisplay(); break;

		// Press F to choose which faces to cull
		case 'f': 
		case 'F':
			face_to_cull=1-face_to_cull;
			glutPostRedisplay(); break;
		
		case 'w':
				view_frame.move_forward();
				glutPostRedisplay(); break;
		case 's':
				view_frame.move_backward();
	    		glutPostRedisplay(); break;
		case 'a':
				view_frame.move_left();
				glutPostRedisplay(); break;
		case 'd':
				view_frame.move_right();
				glutPostRedisplay(); break;
		case 'q':
				view_frame.roll_left();
				glutPostRedisplay(); break;
		case 'e':
				view_frame.roll_right();
				glutPostRedisplay(); break;
			
	}
}

// Special key callback function
void special_key(int key, int x, int y)
{
	switch (key) 
	{	
		
		case GLUT_KEY_LEFT:
			view_frame.turn_left();
			glutPostRedisplay(); break;
		case GLUT_KEY_RIGHT:
			view_frame.turn_right();
			glutPostRedisplay(); break;
		case GLUT_KEY_UP:
			view_frame.look_up();
			glutPostRedisplay(); break;
		case GLUT_KEY_DOWN:
			view_frame.look_down();
			glutPostRedisplay(); break;
		case GLUT_KEY_PAGE_UP:
			view_frame.move_up();
			glutPostRedisplay(); break;
		case GLUT_KEY_PAGE_DOWN:
			view_frame.move_down();
			glutPostRedisplay(); break;
		case GLUT_KEY_HOME:
			xrotate+=30;
            glutPostRedisplay(); break;
        case GLUT_KEY_END:
            yrotate+=30;
            glutPostRedisplay(); break;
        case GLUT_KEY_INSERT:
			zrotate+=30;
            glutPostRedisplay(); break;
	}
}

// Main program
int main(int argc, char* argv[])
{
	glutInit(&argc, argv);
	glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize (600, 500);
	glutInitWindowPosition (100, 50);
	glutCreateWindow ("Draw cylinder");
	init();
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboard);
	glutSpecialFunc(special_key);
	glutMainLoop();
	return 0;
}
